// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef SERVICES_TRACING_PUBLIC_CPP_PERFETTO_JAVA_HEAP_PROFILER_HPROF_PARSER_ANDROID_H_
#define SERVICES_TRACING_PUBLIC_CPP_PERFETTO_JAVA_HEAP_PROFILER_HPROF_PARSER_ANDROID_H_

#include <string>
#include <unordered_map>

#include "base/component_export.h"
#include "base/gtest_prod_util.h"
#include "base/strings/string_piece.h"
#include "services/tracing/public/cpp/perfetto/java_heap_profiler/hprof_buffer_android.h"
#include "services/tracing/public/cpp/perfetto/java_heap_profiler/hprof_instances_android.h"
#include "third_party/perfetto/protos/perfetto/trace/profiling/heap_graph.pbzero.h"

namespace tracing {

using ObjectId = uint64_t;
using RootType = ::perfetto::protos::pbzero::HeapGraphRoot::Type;
using HeapGraphRoot = ::perfetto::protos::pbzero::HeapGraphRoot;
const uint64_t kInvalidObjectId = std::numeric_limits<uint64_t>::max();

// This class takes in a temporary file_path where Java API endpoint
// Debug.dumpHprofData() dumps hprof data to. This file is then parsed for
// references between different instances. The format is defined by said method.
// Refer to:
// https://docs.google.com/document/d/1frGMt8Ro7C6fjbDscImdxHVsFAbSH1mjdETWisZjzVE
// for more information on the file format and
// https://developer.android.com/reference/android/os/Debug#dumpHprofData(java.lang.String)
// for more information on the endpoint.
class COMPONENT_EXPORT(TRACING_CPP) HprofParser {
 public:
  enum ParseResult {
    PARSE_SUCCESS,
    PARSE_FAILED,
    FAILED_TO_OPEN_FILE,
    STRING_ID_NOT_FOUND,
    OBJECT_ID_NOT_FOUND,
  };

  struct ParseStats {
    ParseStats();
    ParseStats(const ParseStats&) = delete;
    ParseStats& operator=(const ParseStats&) = delete;

    // Returns the result of the parser. Set to fail by default.
    ParseResult result = PARSE_FAILED;

    // Number of strings found in heap dump.
    uint64_t num_strings = 0;

    // Number of class objects found in a class heap dump.
    uint64_t num_class_objects = 0;

    // Number of heap dump segments found in a heap dump.
    uint64_t num_heap_dump_segments = 0;

    // Number of class object dumps found within a heap dump segment.
    uint64_t num_class_object_dumps = 0;

    // Number of class instance dumps found within a heap dump segment.
    uint64_t num_class_instance_dumps = 0;

    // Number of object array dumps found within a heap dump segment.
    uint64_t num_object_array_dumps = 0;

    // Number of primitive array dumps found within a heap dump segment.
    uint64_t num_primitive_array_dumps = 0;
  };

  // This stores a reference to the location of the string in the hprof buffer.
  // The data will only be read once when calling the GetString() for the
  // first time. This avoids paging in all strings in the hprof file.
  struct StringReference {
    StringReference(const char* string_position, size_t length);
    ~StringReference();

    const std::string& GetString();

    const char* string_position;
    size_t length = 0;
    std::unique_ptr<std::string> cached_copy;
  };

  HprofParser(const std::string& file_path);
  ~HprofParser();
  HprofParser(const HprofParser&) = delete;
  HprofParser& operator=(const HprofParser&) = delete;

  // This method should only be called after Parse() has been called.
  const ParseStats& parse_stats() const { return parse_stats_; }

  const std::unordered_map<ObjectId, std::unique_ptr<ClassObject>>&
  class_objects() const {
    return class_objects_;
  }

  const std::unordered_map<ObjectId, std::unique_ptr<ClassInstance>>&
  class_instances() const {
    return class_instances_;
  }

  const std::unordered_map<ObjectId, std::unique_ptr<ObjectArrayInstance>>&
  object_array_instances() const {
    return object_array_instances_;
  }

  const std::unordered_map<ObjectId, std::unique_ptr<PrimitiveArrayInstance>>&
  primitive_array_instances() const {
    return primitive_array_instances_;
  }

  const std::unordered_map<RootType, std::vector<ObjectId>>& roots() const {
    return roots_;
  }

  // First opens the file at |file_path_| then passes the data to ParseFileData
  // to parse and record metrics. The hprof file is generated by
  // Debug.dumpHprofData().
  // This method should only ever be run once.
  ParseResult Parse();

 private:
  struct RegisteredNativeSize;
  FRIEND_TEST_ALL_PREFIXES(HprofParserTest, ParseStringTag);
  FRIEND_TEST_ALL_PREFIXES(HprofParserTest, ParseClassTag);
  FRIEND_TEST_ALL_PREFIXES(HprofParserTest, ParseClassObjectDumpSubtag);
  FRIEND_TEST_ALL_PREFIXES(HprofParserTest, ParseClassInstanceDumpSubtag);
  FRIEND_TEST_ALL_PREFIXES(HprofParserTest, ParseObjectArrayDumpSubtag);
  FRIEND_TEST_ALL_PREFIXES(HprofParserTest, ParsePrimitiveArrayDumpSubtag);
  FRIEND_TEST_ALL_PREFIXES(HprofParserTest, ModifyClassObjectTypeNames);

  FRIEND_TEST_ALL_PREFIXES(HprofParserTest,
                           BasicResolveClassInstanceReferences);
  FRIEND_TEST_ALL_PREFIXES(HprofParserTest, ResolveSuperClassObjectFields);
  FRIEND_TEST_ALL_PREFIXES(
      HprofParserTest,
      MultipleInstanceFieldsResolveClassInstanceReferences);
  FRIEND_TEST_ALL_PREFIXES(
      HprofParserTest,
      MissingObjectReferenceResolveClassInstanceReferences);
  FRIEND_TEST_ALL_PREFIXES(
      HprofParserTest,
      ExistingAndMissingReferencesResolveClassInstanceReferences);
  FRIEND_TEST_ALL_PREFIXES(HprofParserTest,
                           BasicResolveObjectArrayInstanceReferences);
  FRIEND_TEST_ALL_PREFIXES(
      HprofParserTest,
      MissingAndExistingReferencesResolveObjectArrayInstanceReferences);
  FRIEND_TEST_ALL_PREFIXES(HprofParserTest, NativeSizeComputation);

  // Parses hprof data file_data and records metrics in parse_stats_.
  void ParseFileData(const unsigned char* file_data, size_t file_size);

  ParseResult ParseStringTag(uint32_t record_length_);
  ParseResult ParseClassTag();
  ParseResult ParseClassObjectDumpSubtag();
  ParseResult ParseClassInstanceDumpSubtag();
  ParseResult ParseObjectArrayDumpSubtag();
  ParseResult ParsePrimitiveArrayDumpSubtag();
  ParseResult ParseHeapDumpTag(uint32_t record_length_);

  void ResolveSuperClassFields();
  ParseResult ResolveClassInstanceReferences();
  ParseResult ComputeNativeSizeOfObjects();
  ParseResult ResolveObjectArrayInstanceReferences();

  RegisteredNativeSize GetRegisteredNativeSize(ClassInstance* cleaner_instance);

  // Append java.lang.Class: to ClassObjects to differentiate them from
  // ClassInstances.
  void ModifyClassObjectTypeNames();

  // Searches through each of the four instance maps: class_objects_,
  // class_instances_, object_array_instances_, and primitive_array_instances_
  // for an instance with given id. If found, return the base instance of the
  // instance found. If not found, return a null pointer.
  Instance* FindInstance(ObjectId id);
  ClassInstance* FindClassInstance(ObjectId id);
  ClassObject* FindClassObject(ObjectId id);
  // Returns true after setting the position of |hprof_buffer_| to point to the
  // |field_name| in class.
  [[nodiscard]] bool SeekToFieldPosition(ClassInstance* instance,
                                         base::StringPiece field_name);

  std::unordered_map<ObjectId, std::unique_ptr<StringReference>> strings_;

  std::unordered_map<ObjectId, std::unique_ptr<ClassObject>> class_objects_;
  std::unordered_map<ObjectId, std::unique_ptr<ClassInstance>> class_instances_;
  std::unordered_map<ObjectId, std::unique_ptr<ObjectArrayInstance>>
      object_array_instances_;
  std::unordered_map<ObjectId, std::unique_ptr<PrimitiveArrayInstance>>
      primitive_array_instances_;

  std::unordered_map<RootType, std::vector<ObjectId>> roots_;

  std::unique_ptr<HprofBuffer> hprof_buffer_;

  const std::string file_path_;
  ParseStats parse_stats_;
};

}  // namespace tracing

#endif  // SERVICES_TRACING_PUBLIC_CPP_PERFETTO_JAVA_HEAP_PROFILER_HPROF_PARSER_ANDROID_H_
